﻿using EnvDTE;
using EnvDTE80;
using Microsoft.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace JoinBoxAlignPlug
{
#pragma warning disable VSTHRD010
    public class CodeLineSuffixRepair : IDisposable
    {
        #region 成员属性
        DTE2 _VsDTE2;
        string _TriggerKey;//关键字符串
        Dictionary<string, FileInfo> _CsSnippet;

        CodeLineSuffixRepairAdd _TextSuffix;
        int _lStartLine = 0;
        int _lStartColumn = 0;
        TextPoint _endPoint;
        EditPoint _objEP;
        TextSelection _selection;
        #endregion

        public CodeLineSuffixRepair(DTE2 vsDTE2, string triggerKey, Dictionary<string, FileInfo> csSnippet)
        {
            _VsDTE2     = vsDTE2;
            _TriggerKey = triggerKey;
            _CsSnippet  = csSnippet;
        }

        /// <summary>
        /// 获取文档信息
        /// </summary>
        public void GetDocEnvironment()
        {
            var doc = _VsDTE2.ActiveDocument;
            if (doc == null)
                return;

            var objTD = (TextDocument)doc.Object();
            // 创建编辑点获取缓冲区所有文本,防止利用选择器造成滚动条改变
            _objEP = objTD.StartPoint.CreateEditPoint();
            _endPoint = objTD.EndPoint;

            _selection = (TextSelection)doc.Selection; //文本选择器
            _lStartLine = _selection.ActivePoint.Line; //光标行
        }

        /// <summary>
        /// 修改文本
        /// </summary>
        /// <param name="upDotIsNull">当前行缺少`就输出true,使得外面可以跳出轮询</param>
        /// <returns>修改行是否成功</returns>
        public bool ChangDocText(out bool upDotIsNull)
        {
            upDotIsNull = false;

            var str1 = _objEP.GetText(_endPoint);
            _lStartColumn = _selection.ActivePoint.LineCharOffset;                                           //光标列
            _TextSuffix = new CodeLineSuffixRepairAdd(str1, _TriggerKey, _lStartLine - 1, _lStartColumn - 1);//行数组是要-1的

            //当前`在注释后就跳出 || 当前点不存在(通常是用户删除了)
            if (_TextSuffix.UpDotLocation == -1)
            {
                upDotIsNull = true;
                return false;
            }

            if (!_TextSuffix.IsCsharpReservedWords())
                return false;

            //遍历制作文件名词典
            var changYes = _TextSuffix.ChangeLine(_CsSnippet);
            if (!changYes)//没有执行修改
                return false;

            //替换文本
            _objEP.ReplaceText(_endPoint,
                _TextSuffix.ToString(),
                (int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers);

            //移动光标到指定行和列
            if (_TextSuffix.SetEndColumn == -1)
                _TextSuffix.SetEndColumn = 0;
            _selection.MoveToLineAndOffset(_lStartLine + _TextSuffix.SetEndLine, _TextSuffix.SetEndColumn);

            //不打算调用格式化,因为太慢
            //vsDTE2.ExecuteCommand("Edit.FormatDocument");
            return true;
        }

        #region Dispose
        public bool Disposed = false;

        /// <summary>
        /// 显式调用Dispose方法,继承IDisposable
        /// </summary>
        public void Dispose()
        {
            //由手动释放
            Dispose(true);
            //通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 析构函数,以备忘记了显式调用Dispose方法
        /// </summary>
        ~CodeLineSuffixRepair()
        {
            Dispose(false); //由系统释放
        }

        /// <summary>
        /// 释放
        /// </summary>
        /// <param name="ing"></param>
        protected virtual void Dispose(bool ing)
        {
            if (Disposed) //不重复释放
                return;
            Disposed = true;  //让类型知道自己已经被释放

            _TextSuffix.Dispose();
        }
        #endregion
    }

    public class CodeLineSuffixRepairAdd : IDisposable
    {
        #region 成员属性
        public int SetEndLine = -1;   //控制光标位置.行
        public int SetEndColumn = -1; //控制光标位置.列

        StringBuilder _space;         //补充空格
        string _triggerKey;           //关键字符串
        List<string> _strs;           //所有文本
        string _systemKeyword;        //c#保留词
        CSharpCodeProvider _cs;       //检测是否为c#保留词

        const int Indentation = 4;    //缩进空格(因为不知道怎么拿vs的配置,所以就这样吧)
        int _lStartLine = 0;          // 光标行
        int _lStartColumn = 0;        // 光标列

        /// <summary>
        /// 有效代码,非注释范围
        /// </summary>
        string CodeEfficient
        {
            get
            {
                if (_CodeEfficient == null)
                {
                    var a          = _strs[_lStartLine].Substring(0, _lStartColumn);
                    _CodeEfficient = CodeHelper.GetCode(a);
                }
                return _CodeEfficient;
            }
        }
        string _CodeEfficient;

        /// <summary>
        /// 行中有效代码`位置
        /// 缺少`==-1
        /// </summary>
        public int UpDotLocation
        {
            get
            {
                if (_UpDotLocation == null)
                    _UpDotLocation = CodeEfficient.LastIndexOf(_triggerKey);
                return _UpDotLocation.Value;
            }
        }
        int? _UpDotLocation;
        #endregion

        /// <summary>
        /// 后缀补全
        /// </summary>
        /// <param name="str1">所有行</param>
        /// <param name="keyword">关键后缀</param>
        /// <param name="startLine">光标行</param>
        /// <param name="startColumn">光标列</param>
        public CodeLineSuffixRepairAdd(string str1, string keyword, int startLine, int startColumn)
        {
            _strs = new List<string>();
            _strs.AddRange(str1.Split(new string[] { Environment.NewLine }, StringSplitOptions.None));
            _triggerKey = keyword;
            _cs = new CSharpCodeProvider();
            _lStartLine = startLine;
            _lStartColumn = startColumn;
            _space = new StringBuilder();
        }

        public override string ToString()
        {
            return string.Join(Environment.NewLine, _strs);
        }

        /// <summary>
        /// 输入是否满足c#保留词
        /// </summary>
        public bool IsCsharpReservedWords()
        {
            //这里是并不会重复获取,只有本类中调用的属性才会重复获取,因此这里做成方法
            bool isCsharpReservedWords;

            //切割"`if"出来
            var upDotLocation = UpDotLocation;
            if (upDotLocation == -1)
                throw new Exception("IsCsharpReservedWords 的 CodeEfficientUpDotLocation == -1");

            ++upDotLocation;

            _systemKeyword = CodeEfficient.Substring(upDotLocation, CodeEfficient.Length - upDotLocation);//就只有"if"

            // 因为是光标列位置剔除的,所以这里只有if了并没有注释
            // 这里可以处理很奇葩的注释  `的后面字符串 "if /*aa*/wwwwwwww/*a\\\"a\\\"a*/nnnnnnnn/*b\\\"b\\\"b*/"
            // var cds = CodeHelper.GetCommentCodes(_systemKeyword);
            // if (cds.Count > 0)
            //     _systemKeyword = cds[0];

            _systemKeyword = _systemKeyword.Trim(' ', '\t').ToLower();
            //c#保留词
            //var test = cs.IsValidIdentifier("new");   // returns false
            isCsharpReservedWords = !_cs.IsValidIdentifier(_systemKeyword);

            return isCsharpReservedWords;
        }

        //可替换目标
        string[] DeclarationsWords = new string[] { "$expression$", "$collection$" };
        //控制光标位置
        const string DeclarationsEnd = "$end$";

        public bool ChangeLine(Dictionary<string, FileInfo> snippetDic)
        {
            if (UpDotLocation == -1)
                throw new Exception("ChangeLine() 的 CodeEfficientUpDotLocation == -1");

            //控制光标位置
            SetEndLine = -1;

            //在代码片段中找到对应的
            string snippetFullName = null;
            if (snippetDic.ContainsKey(_systemKeyword))
                snippetFullName = snippetDic[_systemKeyword].FullName;
            if (snippetFullName == null)
                return false;

            //读取代码片段文件
            var xmlDoc = new XmlDocument();
            xmlDoc.Load(snippetFullName);
            var snippet = new SnippetXmlCodes(xmlDoc);

            var snippetCode = snippet.Code;
            string repOk = null;
            //替换$$关键字
            foreach (var item in DeclarationsWords)
            {
                var sn = snippetCode.IndexOf(item);
                if (sn != -1)
                    repOk = item;
            }
            //替换是否成功
            if (repOk == null)
                return false;


            //获取行的内容,行内容插入到代码片段的符号上
            GetInfo(out string codeHead, out string lineHeadSpace, out string indentationSpace);
            snippetCode = snippetCode.Replace(repOk, codeHead);

            var snippetCodes = snippetCode.Split(Environment.NewLine.ToCharArray());
            snippetCodes = snippetCodes.Where(a => !string.IsNullOrEmpty(a)).ToArray();//去除空数组

            //处理代码行内容
            //把行首\t变成 lineHeadSpace ,行其他\t变成 indentationSpace
            var strList = new List<string>(snippetCodes);
            var regex = RegexBetweenSymbols();

            //代码行替换\t和获取鼠标end位置
            for (int i = 0; i < strList.Count; i++)
            {
                string str = strList[i];
                if (str[0] == '\t')
                    str = lineHeadSpace + str.Substring(1, strList[i].Length - 1);
                str = str.Replace("\t", indentationSpace);
                strList[i] = str;

                var column = str.IndexOf(DeclarationsEnd);
                if (column != -1)//必须要提前去除空数组,否则这里获取不正确
                {
                    SetEndLine   = i;
                    SetEndColumn = regex.Replace(str.Substring(0, column).TrimEnd(), "").Length;
                }
            }

            const string meiyuan = "$";
            //代码行内所有$$符号内替换为默认内容
            for (int i = 0; i < strList.Count; i++)
            {
                //行内容替换为默认的
                foreach (var item in snippet.Declarations)
                {
                    var id     = meiyuan + item.ID + meiyuan;
                    strList[i] = strList[i].Replace(id, item.Default);
                }
                //某些没有默认,行内出现$$中间的任何内容都替换为空白
                strList[i] = regex.Replace(strList[i], "");
            }

            //第一行是特别的,有注释
            var sb = new StringBuilder();
            sb.Append(lineHeadSpace);
            sb.Append(strList[0]);
            int codeComments = GetCodeComment(out string comment);
            if (codeComments == 1)
                sb.Append(comment);
            _strs[_lStartLine] = sb.ToString();

            //第二行起
            for (int i = 1; i < strList.Count; i++)
                _strs.Insert(_lStartLine + i, strList[i]);
            return true;
        }

        /// <summary>
        /// 正则在符号之间
        /// </summary>
        /// <returns></returns>
        Regex RegexBetweenSymbols()
        {
            //var text = "亲爱的#customer#，您已成功购买了#brand#的商品，订单编号为#order#，请凭订单编号或⼿机号⾄商家处消费！#url# 请保留此短信，及时查看消费情况哦！";
            //var rex2 = new System.Text.RegularExpressions.Regex(@"\#(.+?)\#");
            //rex2.Matches(text);
            //==> MatchCollection(4) { [#customer#], [#brand#], [#order#], [#url#] }

            //正则获取字符之间
            //var rex = new Regex("(?<=\\$).*(?=\\$)");//selected$ $end  这里找到是这样的
            return new Regex(@"\$(.+?)\$");  //[selected] [end] 我想找到这样的
                                             //var str = "    $selected$ $end$";//要删除前面的,保留后面呢?

            /*
            //正则去掉字符串中最前面连续的几个字符串 https://zhidao.baidu.com/question/2118729617818063547.html
            using System.Text.RegularExpressions;
            string input = "AABBBACCCCCAABBBB";
            var reg = new Regex(@"(.)\1+((?:(?!\1).)?.*)");
            reg.Replace(input, "$2")
            */
        }

        /// <summary>
        /// 获取行内容
        /// </summary>
        /// <param name="codeHead">行有效代码</param>
        /// <param name="lineHeadSpace">行头空格</param>
        /// <param name="indentationSpace">行缩进空格</param>
        void GetInfo(out string codeHead, out string lineHeadSpace, out string indentationSpace)
        {
            //为了避免调用格式化(太慢),数前面有几个空格
            var code = _strs[_lStartLine];
            var spaceNum = code.Length - code.TrimStart(' ').Length;

            //代码部分
            codeHead = CodeEfficient.Substring(0, UpDotLocation).TrimEnd('.', ';');

            //行头空格
            _space.Clear();
            for (int i = 0; i < spaceNum; i++)
                _space.Append(' ');
            lineHeadSpace = _space.ToString();

            //缩进空格
            _space.Clear();
            for (int i = 0; i < Indentation; i++)
                _space.Append(' ');
            indentationSpace = _space.ToString();
        }

        int GetCodeComment(out string codeComment)
        {
            //如果注释==1,直接放到if()后面
            codeComment = _strs[_lStartLine].Substring(_lStartColumn, _strs[_lStartLine].Length - _lStartColumn);

            //全部放if后面就算了
            //var codeComments = CodeHelper.GetCommentTexts(codeComment);
            //int num = codeComments.Count;
            //如果注释大于1,直接放到if(){}尾巴

            int num = 1;
            return num;
        }

        #region Dispose
        public bool Disposed = false;

        /// <summary>
        /// 显式调用Dispose方法,继承IDisposable
        /// </summary>
        public void Dispose()
        {
            //由手动释放
            Dispose(true);
            //通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 析构函数,以备忘记了显式调用Dispose方法
        /// </summary>
        ~CodeLineSuffixRepairAdd()
        {
            Dispose(false); //由系统释放
        }

        /// <summary>
        /// 释放
        /// </summary>
        /// <param name="ing"></param>
        protected virtual void Dispose(bool ing)
        {
            if (Disposed) //不重复释放
                return;
            Disposed = true;  //让类型知道自己已经被释放
        }
        #endregion
    }
#pragma warning restore VSTHRD010
}